// 版权所有2016 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包httptrace提供了跟踪
// HTTP客户端请求中的事件的机制。
package httptrace

import (
	"context"
	"crypto/tls"
	"internal/nettrace"
	"net"
	"net/textproto"
	"reflect"
	"time"
)

// 防止赋值的唯一类型。
type clientEventContextKey struct{}

// ContextClientTrace返回与
// 提供的上下文关联的ClientTrace。如果没有，则返回nil。
func ContextClientTrace(ctx context.Context) *ClientTrace {
	trace, _ := ctx.Value(clientEventContextKey{}).(*ClientTrace)
	return trace
}

// WithClientTrace返回基于提供的父
// ctx的新上下文。使用返回的上下文发出的HTTP客户端请求将使用
// 提供的跟踪钩子，以及之前向ctx注册的任何钩子
// 。在提供的跟踪中定义的任何钩子都将首先被调用。
func WithClientTrace(ctx context.Context, trace *ClientTrace) context.Context {
	if trace == nil {
		panic("nil trace")
	}
	old := ContextClientTrace(ctx)
	trace.compose(old)

	ctx = context.WithValue(ctx, clientEventContextKey{}, trace)
	if trace.hasNetHooks() {
		nt := &nettrace.Trace{
			ConnectStart: trace.ConnectStart,
			ConnectDone:  trace.ConnectDone,
		}
		if trace.DNSStart != nil {
			nt.DNSStart = func(name string) {
				trace.DNSStart(DNSStartInfo{Host: name})
			}
		}
		if trace.DNSDone != nil {
			nt.DNSDone = func(netIPs []interface{}, coalesced bool, err error) {
				addrs := make([]net.IPAddr, len(netIPs))
				for i, ip := range netIPs {
					addrs[i] = ip.(net.IPAddr)
				}
				trace.DNSDone(DNSDoneInfo{
					Addrs:     addrs,
					Coalesced: coalesced,
					Err:       err,
				})
			}
		}
		ctx = context.WithValue(ctx, nettrace.TraceKey{}, nt)
	}
	return ctx
}

// ClientTrace是一组钩子，可在传出
// HTTP请求的各个阶段运行。任何特定的挂钩都可能为零。函数可以是
// 从不同的goroutine并发调用，有些函数可以在请求完成或失败后被称为
// 函数。
// 
// ClientTrace当前在单个往返过程中跟踪单个HTTP请求和响应
// 并且没有跨越一系列重定向请求的挂钩。
// 
// 见https:
type ClientTrace struct {
	// 在创建连接或从空闲池检索
	// 之前调用GetConn。主机端口是目标或代理的
	// /“主机：端口”。如果已经有空闲的缓存连接可用，则GetConn甚至被称为
	// 。
	GetConn func(hostPort string)

	// 成功连接后调用GotConn 
	// 获取。没有无法获得
	// 连接的钩子；相反，使用
	// Transport.RoundTrip中的错误。
	GotConn func(GotConnInfo)

	// 当连接返回到
	// 空闲池时，调用putidlecon。如果err为nil，则连接为
	// 已成功返回到空闲池。如果err为非零，则
	// 它描述了为什么不为零。如果
	// 通过Transport.DisableKeepAlives禁用了连接重用，则不会调用Putidlecon。
	// 在调用方的Response.Body.Close之前调用putidlecon 
	// 调用返回。
	// 对于HTTP/2，当前未使用此挂钩。
	PutIdleConn func(err error)

	// 当响应的第一个字节
	// 头可用时，调用GotFirstResponseByte。如果服务器以“100 
	GotFirstResponseByte func()

	// Continue”响应进行响应，则调用Got100Continue。
	Got100Continue func()

	// 为每个1x信息响应头调用Got1xxResponse 
	// 在最终非1x响应之前返回。对于“100 Continue”响应，Got1xxResponse称为
	// 即使还定义了Got100Continue。
	// 如果返回错误，则客户端请求将以该错误值中止。
	Got1xxResponse func(code int, header textproto.MIMEHeader) error

	// DNSStart在DNS查找开始时调用。
	DNSStart func(DNSStartInfo)

	// DNSDone在DNS查找结束时调用。
	DNSDone func(DNSDoneInfo)

	// 当新连接的拨号开始时调用ConnectStart。
	// 如果启用了net.Dialer.DualStack（IPv6“快乐眼球”）支持
	// 可能会多次调用。
	ConnectStart func(network, addr string)

	// 在新连接的拨号
	// 完成时调用ConnectDone。提供的错误指示
	// 连接是否成功完成。
	// 如果启用了net.Dialer.DualStack（“快乐眼球”）支持
	// 可能会多次调用。
	ConnectDone func(network, addr string, err error)

	// TLS握手开始时调用TLSHandshakeStart。当
	// 通过HTTP代理连接到HTTPS站点时，握手在代理处理连接请求后发生。
	TLSHandshakeStart func()

	// TLShandShakeOne在TLS握手后调用，该TLS握手具有
	// 成功握手的连接状态，或握手时出现非零错误
	// 失败。
	TLSHandshakeDone func(tls.ConnectionState, error)

	// WroteHeaderField在传输写入每个请求头后调用。在进行此调用时，值
	// 可能已缓冲，尚未写入网络。
	WroteHeaderField func(key string, value []string)

	// 在传输写入
	// 所有请求头之后调用WroteHeaders。如果请求指定了
	WroteHeaders func()

	// Wait100Continue
	// “Expect:100 continue”，并且传输已写入
	// 请求头，但正在等待来自
	// 在写入请求正文之前，先访问服务器。
	Wait100Continue func()

	// WroteRequest被调用，其结果是写入
	// 请求和任何主体。在重试请求的情况下，可以多次调用
	// 。
	WroteRequest func(WroteRequestInfo)
}

// WroteRequestInfo包含提供给WroteRequest的信息
// hook。
type WroteRequestInfo struct {
	// Err是写入请求时遇到的任何错误。
	Err error
}

// compose修改t，使其尊重以前在旧版中注册的钩子，
// /遵循t.compose中请求的组合策略。
func (t *ClientTrace) compose(old *ClientTrace) {
	if old == nil {
		return
	}
	tv := reflect.ValueOf(t).Elem()
	ov := reflect.ValueOf(old).Elem()
	structType := tv.Type()
	for i := 0; i < structType.NumField(); i++ {
		tf := tv.Field(i)
		hookType := tf.Type()
		if hookType.Kind() != reflect.Func {
			continue
		}
		of := ov.Field(i)
		if of.IsNil() {
			continue
		}
		if tf.IsNil() {
			tf.Set(of)
			continue
		}

		// 复制一份tf供tf调用。（否则它将创建递归调用周期和堆栈溢出）
		tfCopy := reflect.ValueOf(tf.Interface())

		// 我们需要以某种顺序调用tf和of。
		newFunc := reflect.MakeFunc(hookType, func(args []reflect.Value) []reflect.Value {
			tfCopy.Call(args)
			return of.Call(args)
		})
		tv.Field(i).Set(newFunc)
	}
}

// DNSStartInfo包含有关DNS请求的信息。
type DNSStartInfo struct {
	Host string
}

// DNSDoneInfo包含有关DNS查找结果的信息。
type DNSDoneInfo struct {
	// 地址是在DNS 
	// 查找中找到的IPv4和/或IPv6地址。切片的内容不应发生变异。
	Addrs []net.IPAddr

	// Err是DNS查找过程中发生的任何错误。
	Err error

	// Coalesced是指地址是否与同时执行相同DNS查找的另一个
	// 调用方共享。
	Coalesced bool
}

func (t *ClientTrace) hasNetHooks() bool {
	if t == nil {
		return false
	}
	return t.DNSStart != nil || t.DNSDone != nil || t.ConnectStart != nil || t.ConnectDone != nil
}

// GotConnInfo是ClientTrace.GotConn函数的参数，
// 包含有关所获得连接的信息。
type GotConnInfo struct {
	// Conn是已获得的连接。它属于http.Transport的
	// 所有，不应由ClientTrace的用户读取、写入或关闭。
	Conn net.Conn

	// 重用是指此连接以前是否已用于另一个HTTP请求。
	Reused bool

	// WasIdle是指此连接是否从
	// 空闲池获得。
	WasIdle bool

	// IdleTime报告以前连接的空闲时间
	// idle，如果WasIdle为true。
	IdleTime time.Duration
}
